package com.gitee.sop.layui.adminserver.module.background.system.api.isv;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gitee.easyopen.doc.DataType;
import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import com.gitee.easyopen.doc.annotation.ApiDocMethod;
import com.gitee.easyopen.util.CopyUtil;
import com.gitee.sop.layui.adminserver.module.background.system.api.IdParam;
import com.gitee.sop.layui.adminserver.module.background.system.api.isv.param.*;
import com.gitee.sop.layui.adminserver.module.background.system.api.isv.result.*;
import com.gitee.sop.layui.adminserver.module.background.system.bean.ChannelMsg;
import com.gitee.sop.layui.adminserver.module.background.system.bean.NacosConfigs;
import com.gitee.sop.layui.adminserver.module.background.system.common.ChannelOperation;
import com.gitee.sop.layui.adminserver.module.background.system.common.IdGen;
import com.gitee.sop.layui.adminserver.module.background.system.common.RSATool;
import com.gitee.sop.layui.adminserver.module.background.system.entity.IsvInfo;
import com.gitee.sop.layui.adminserver.module.background.system.entity.IsvKeys;
import com.gitee.sop.layui.adminserver.module.background.system.entity.PermIsvRole;
import com.gitee.sop.layui.adminserver.module.background.system.entity.PermRole;
import com.gitee.sop.layui.adminserver.module.background.system.mapper.IsvInfoMapper;
import com.gitee.sop.layui.adminserver.module.background.system.mapper.PermRoleMapper;
import com.gitee.sop.layui.adminserver.module.background.system.service.ConfigPushService;
import com.gitee.sop.layui.adminserver.module.background.system.service.IsvKeysService;
import com.gitee.sop.layui.adminserver.module.background.system.service.PermIsvRoleService;
import com.gitee.sop.layui.adminserver.module.background.system.service.RoutePermissionService;
import com.gitee.sop.layui.adminserver.module.base.constants.Result;
import com.gitee.sop.layui.adminserver.module.base.error.ServiceInterruptionException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.NotBlank;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author admin
 */
@RestController
@ApiDoc("ISV管理")
@Slf4j
public class IsvApi {

    public static final byte SIGN_TYPE_RSA = 1;
    public static final byte SIGN_TYPE_MD5 = 2;

    static Map<String, Byte> SIGN_TYPE_MAP = new HashMap<>();

    static {
        SIGN_TYPE_MAP.put("rsa", (byte) SIGN_TYPE_RSA);
        SIGN_TYPE_MAP.put("md5", (byte) SIGN_TYPE_MD5);
    }

    @Autowired
    IsvInfoMapper isvInfoMapper;

    @Autowired
    IsvKeysService isvKeysService;

    @Autowired
    PermIsvRoleService permIsvRoleService;

    @Autowired
    PermRoleMapper permRoleMapper;

    @Autowired
    RoutePermissionService routePermissionService;

    @Autowired
    private ConfigPushService configPushService;

    @Value("${sop.sign-type}")
    private String sopSignType;

    @GetMapping("/api/isv.info.page")
    @ApiDocMethod(description = "isv列表", results = {
            @ApiDocField(name = "pageIndex", description = "第几页", dataType = DataType.INT, example = "1"),
            @ApiDocField(name = "pageSize", description = "每页几条数据", dataType = DataType.INT, example = "10"),
            @ApiDocField(name = "total", description = "每页几条数据", dataType = DataType.LONG, example = "100"),
            @ApiDocField(name = "rows", description = "数据", dataType = DataType.ARRAY, elementClass = IsvInfoVO.class)
    })
    Result pageIsv(IsvPageParam param) {
        Page<IsvInfo> page = new Page<>(param.getPageIndex(), param.getPageSize());
        LambdaQueryWrapper<IsvInfo> wrapper = new QueryWrapper<IsvInfo>().lambda();
        wrapper.likeRight(StringUtils.isNotBlank(param.getAppKey()), IsvInfo::getAppKey, param.getAppKey());
        wrapper.orderByDesc(IsvInfo::getId);
        isvInfoMapper.selectPage(page, wrapper);
        List<IsvInfo> list = page.getRecords();
        List<IsvInfoVO> retList = list.stream()
                .map(isvInfo -> {
                    return buildIsvVO(isvInfo);
                })
                .collect(Collectors.toList());
        return Result.successPage(retList, page.getTotal(), page.getCurrent(), page.getSize());
    }

    @GetMapping("/api/isv.info.get")
    @ApiDocMethod(description = "获取isv")
    Result getIsvVO(IdParam param) {
        IsvInfo isvInfo = isvInfoMapper.selectById(param.getId());
        IsvInfoVO isvInfoVO = buildIsvVO(isvInfo);
        return Result.success(isvInfoVO);
    }

    @GetMapping("/api/isv.keys.get")
    @ApiDocMethod(description = "获取isv2")
    Result getIsvKeys(@NotBlank(message = "appKey不能为空")
                      @ApiDocField(description = "appKey")
                              String appKey) {
        IsvKeys isvKeys = isvKeysService.getByAppKey(appKey);
        IsvKeysVO isvDetailVO = new IsvKeysVO();
        if (isvKeys != null) {
            CopyUtil.copyProperties(isvKeys, isvDetailVO);
        }
        isvDetailVO.setAppKey(appKey);
        isvDetailVO.setSignType(getSignType());
        return Result.success(isvDetailVO);
    }

    private IsvInfoVO buildIsvVO(IsvInfo isvInfo) {
        if (isvInfo == null) {
            return null;
        }
        IsvInfoVO vo = new IsvInfoVO();
        CopyUtil.copyProperties(isvInfo, vo);
        vo.setRoleList(this.buildIsvRole(isvInfo));
        return vo;
    }

    /**
     * 构建ISV拥有的角色
     *
     * @param permClient
     * @return
     */
    List<RoleVO> buildIsvRole(IsvInfo permClient) {
        List<String> roleCodeList = routePermissionService.listClientRoleCode(permClient.getId());
        if (CollectionUtils.isEmpty(roleCodeList)) {
            return Collections.emptyList();
        }
        List<PermRole> list = permRoleMapper.selectList(new QueryWrapper<PermRole>().lambda().in(PermRole::getRoleCode, roleCodeList));
        return list.stream()
                .map(permRole -> {
                    RoleVO vo = new RoleVO();
                    CopyUtil.copyProperties(permRole, vo);
                    return vo;
                })
                .collect(Collectors.toList());
    }

    @PostMapping("/api/isv.info.add")
    @ApiDocMethod(description = "添加isv")
    @Transactional(rollbackFor = Exception.class)
    public Result addIsv(IsvInfoFormAdd param) throws Exception {
        String appKey = new SimpleDateFormat("yyyyMMdd").format(new Date()) + IdGen.nextId();
        IsvInfo rec = new IsvInfo();
        rec.setAppKey(appKey);
        CopyUtil.copyPropertiesIgnoreNull(param, rec);
        isvInfoMapper.insert(rec);
        if (CollectionUtils.isNotEmpty(param.getRoleCode())) {
            this.saveIsvRole(rec, param.getRoleCode());
        }
        IsvKeysGenVO isvKeysGenVO = this.createIsvKeys();
        IsvKeys isvKeys = new IsvKeys();
        isvKeys.setAppKey(appKey);
        isvKeys.setSignType(getSignType());
        CopyUtil.copyPropertiesIgnoreNull(isvKeysGenVO, isvKeys);
        isvKeysService.save(isvKeys);

        if (CollectionUtils.isEmpty(param.getRoleCode())) {
            List<String> roleCodeList = new ArrayList<>();
            roleCodeList.add("open");
            this.saveIsvRole(rec, roleCodeList);
        }

        this.sendChannelMsg(rec.getAppKey());
        return Result.success();
    }

    private byte getSignType() {
        return SIGN_TYPE_MAP.getOrDefault(sopSignType, SIGN_TYPE_RSA);
    }

    @PostMapping("/api/isv.info.update")
    @ApiDocMethod(description = "修改isv")
    @Transactional(rollbackFor = Exception.class)
    public Result updateIsv(IsvInfoFormUpdate param) {
        IsvInfo rec = isvInfoMapper.selectById(param.getId());
        CopyUtil.copyPropertiesIgnoreNull(param, rec);
        isvInfoMapper.updateById(rec);
        this.saveIsvRole(rec, param.getRoleCode());
        this.sendChannelMsg(rec.getAppKey());
        return Result.success();
    }

    @PostMapping("/api/isv.info.updateStatus")
    @ApiDocMethod(description = "修改isv状态")
    @Transactional(rollbackFor = Exception.class)
    public Result updateStatus(IsvInfoFormUpdate param) {
        IsvInfo rec = isvInfoMapper.selectById(param.getId());
        CopyUtil.copyPropertiesIgnoreNull(param, rec);
        isvInfoMapper.updateById(rec);
        this.sendChannelMsg(rec.getAppKey());
        return Result.success();
    }

    @PostMapping("/api/isv.keys.update")
    @ApiDocMethod(description = "修改isv")
    public Result updateIsvKeys(IsvKeysFormUpdate param) {
        IsvKeys isvKeys = isvKeysService.getByAppKey(param.getAppKey());
        if (isvKeys == null) {
            isvKeys = new IsvKeys();
            CopyUtil.copyPropertiesIgnoreNull(param, isvKeys);
            isvKeys.setSignType(getSignType());
            isvKeysService.save(isvKeys);
        } else {
            CopyUtil.copyPropertiesIgnoreNull(param, isvKeys);
            isvKeysService.updateById(isvKeys);
        }

        this.sendChannelMsg(isvKeys.getAppKey());
        return Result.success();
    }

    private void sendChannelMsg(String appKey) {
        IsvDetailVO isvDetail = isvInfoMapper.getIsvDetail(appKey);
        if (isvDetail == null) {
            return;
        }
        ChannelMsg channelMsg = new ChannelMsg(ChannelOperation.ISV_INFO_UPDATE, isvDetail);
        configPushService.publishConfig(NacosConfigs.DATA_ID_ISV, NacosConfigs.GROUP_CHANNEL, channelMsg);
    }

    private IsvKeysGenVO createIsvKeys() throws Exception {
        IsvKeysGenVO isvFormVO = new IsvKeysGenVO();
        String secret = IdGen.uuid();

        isvFormVO.setSecret(secret);

        RSATool rsaToolIsv = new RSATool(RSATool.KeyFormat.PKCS8, RSATool.KeyLength.LENGTH_2048);
        RSATool.KeyStore keyStoreIsv = rsaToolIsv.createKeys();
        isvFormVO.setPublicKeyIsv(keyStoreIsv.getPublicKey());
        isvFormVO.setPrivateKeyIsv(keyStoreIsv.getPrivateKey());

        RSATool platFormRsaToolIsv = new RSATool(RSATool.KeyFormat.PKCS8, RSATool.KeyLength.LENGTH_2048);
        RSATool.KeyStore platFormKeyStoreIsv = platFormRsaToolIsv.createKeys();
        isvFormVO.setPublicKeyPlatform(platFormKeyStoreIsv.getPublicKey());
        isvFormVO.setPrivateKeyPlatform(platFormKeyStoreIsv.getPrivateKey());
        return isvFormVO;
    }

    @PostMapping("/api/isv.keys.gen")
    @ApiDocMethod(description = "生成公私钥")
    Result createPubPriKey(IsvKeysGen param) throws Exception {
        RSATool.KeyFormat format = RSATool.KeyFormat.PKCS8;
        Byte keyFormat = param.getKeyFormat();
        if (keyFormat != null && keyFormat == 2) {
            format = RSATool.KeyFormat.PKCS1;
        }
        RSATool rsaTool = new RSATool(format, RSATool.KeyLength.LENGTH_2048);
        return Result.success(rsaTool.createKeys());
    }

    @GetMapping(name = "/api/isv.secret.gen")
    @ApiDocMethod(description = "生成MD秘钥")
    String createSecret() throws Exception {
        return IdGen.uuid();
    }

    void saveIsvRole(IsvInfo isvInfo, List<String> roleCodeList) {
        long isvInfoId = isvInfo.getId();
        permIsvRoleService.remove(new QueryWrapper<PermIsvRole>().lambda().eq(PermIsvRole::getIsvId, isvInfoId));

        List<PermIsvRole> tobeSaveList = roleCodeList.stream()
                .map(roleCode -> {
                    PermIsvRole rec = new PermIsvRole();
                    rec.setIsvId(isvInfoId);
                    rec.setRoleCode(roleCode);
                    return rec;
                })
                .collect(Collectors.toList());

        if (CollectionUtils.isNotEmpty(tobeSaveList)) {
            permIsvRoleService.saveBatch(tobeSaveList);
        }

        try {
            routePermissionService.sendIsvRolePermissionMsg(isvInfo.getAppKey(), roleCodeList);
        } catch (Exception e) {
            log.error("同步角色失败，isvInfo:{}, roleCodeList:{}", isvInfo, roleCodeList);
            throw ServiceInterruptionException.fail(500, "同步角色失败，请查看网关日志");
        }
    }
}
